<?php

class Nuo
{

	/**
	 * @var string
	 */
	static protected $_pageName = 'core_index_index';

	/**
	 * @property User_Model_User
	 */
	static protected $_viewer = NULL;
	/**
	 * available '', 'admin'
	 * @property string
	 */
	static protected $_sideMode = '';

	/**
	 * @property int
	 */
	static protected $_siteId = 1;

	/**
	 * @property int
	 */
	static protected $_errorReporting = NUO_ERROR_REPORTING;

	/**
	 * @property int
	 */
	static protected $_errorCode = null;

	/**
	 * Nuo
	 * @property Nuo
	 */
	static protected $_instance;

	/**
	 * @property array
	 */
	static protected $_hooks = array();

	/**
	 * @param array
	 */
	static protected $_modules = array();

	/**
	 * @property string
	 */
	static protected $_tablePrefix = 'nuo_';

	/**
	 * @property Core_Api_Setting
	 */
	static protected $_apiSetting = null;

	/**
	 * get current site id
	 * @return int
	 */
	static public function getSiteId()
	{
		return self::$_siteId;
	}

	/**
	 * set current site id
	 * @param int $site_id
	 * @return void
	 */
	static public function setSiteId($site_id)
	{
		self::$_siteId = $site_id;
	}

	/**
	 * @property string
	 */
	static public function getTablePrefix()
	{
		return self::$_tablePrefix;
	}

	/**
	 * @param string
	 * @return void
	 */
	static public function setTablePrefix($string)
	{
		self::$_tablePrefix = $string;
	}

	/**
	 * get Nuo
	 * @return Nuo
	 */
	static public function getInstance()
	{
		return (null == self::$_instance) ? (self::$_instance = new self) : self::$_instance;
	}

	/**
	 * call event
	 * @param string $eventName
	 * @param object $resource
	 * @param array $params
	 * @return void
	 */
	static public function call($eventName, $resource = NULL, $params = NULL)
	{

		if (isset(self::$_hooks[$eventName]))
		{
			foreach (self::$_hooks[$eventName] as $function)
			{
				call_user_func_array($function, array(
						$resource,
						$params
				));
			}
		}
	}

	static public function autoload($class)
	{
		$arr = explode('_', $class);
		$plugin = array_shift($arr);
		$type = array_shift($arr);

		if (is_readable($filename = APPLICATION_PATH . '/modules/' . $plugin . '/' . strtolower($type) . 's/' . implode('/', $arr) . '.php'))
		{
			require_once $filename;
			return true;
		}
		else
		if (is_readable($filename = APPLICATION_PATH . '/modules/' . $plugin . '/' . $type . '/' . implode('/', $arr) . '.php'))
		{
			require_once $filename;
			return true;
		}
		return false;
	}

	/**
	 * camel case word spliter
	 * @param string
	 * @return string
	 */
	static public function inflect($string)
	{
		return str_replace(' ', '', ucwords(str_replace(array(
				'.',
				'-'
		), ' ', $string)));
	}

	/**
	 * lower case, middle score spliter
	 * @param string
	 * @return string
	 */
	static public function deflect($string)
	{
		return strtolower(trim(preg_replace('/([a-z0-9])([A-Z])/', '\1-\2', $string), '-. '));
	}

	/**
	 * Register error handlers
	 */
	static public function registerErrorHandlers()
	{

		// Set mask
		self::$_errorReporting = E_ERROR | E_COMPILE_ERROR | E_COMPILE_WARNING | E_CORE_ERROR | E_CORE_WARNING | E_ERROR | E_PARSE | E_RECOVERABLE_ERROR | E_USER_ERROR;

		// Register
		set_error_handler(array(
				'Nuo',
				'handleError'
		));

		set_exception_handler(array(
				'Nuo',
				'handleException'
		));
	}

	/**
	 * Error handler
	 *
	 * @param integer $errno
	 * @param string $errstr
	 * @param string $errfile
	 * @param integer $errline
	 * @param array $errcontext
	 */
	static public function handleError($errno, $errstr, $errfile = null, $errline = null, array $errcontext = null)
	{
		// Check log
		if (Zend_Registry::isRegistered('Zend_Log'))
		{

			// Force fatal errors to get reported
			$reporting = error_reporting() | self::$_errorReporting;
			$fatal = false;

			// Log
			if ($reporting & $errno)
			{
				$log = Zend_Registry::get('Zend_Log');
				switch( $errno ) {
					case E_USER_NOTICE :
					case E_NOTICE :
						$level = Zend_Log::NOTICE;
						break;
					default :
					case E_USER_WARNING :
					case E_WARNING :
					case E_COMPILE_WARNING :
					case E_CORE_WARNING :
					case E_DEPRECATED :
					case E_STRICT :
					case E_USER_DEPRECATED :
						$level = Zend_Log::WARN;
						break;
					case E_COMPILE_ERROR :
					case E_CORE_ERROR :
					case E_ERROR :
					case E_PARSE :
					case E_RECOVERABLE_ERROR :
					case E_USER_ERROR :
						$level = Zend_Log::ERR;
						$fatal = true;
						break;
				}

				self::getErrorCode(true);
				$backtrace = self::formatBacktrace(array_slice(debug_backtrace(), 1));
				$log -> log(PHP_EOL . sprintf('[%1$d] %2$s (%3$s) [%4$d]' . PHP_EOL . '%5$s', $errno, $errstr, str_replace(ROOT_PATH, '', $errfile), $errline, $backtrace), $level);
			}

			// Handle fatal with nice response for user
			if ($fatal)
			{
				self::_sendFatalResponse();
			}

		}

		return !defined('NUO_ERROR_SILENCE') || NUO_ERROR_SILENCE;
	}

	/**
	 * Exception handler
	 *
	 * @param Exception $exception
	 */
	static public function handleException($exception)
	{

		// If no log, just do normally?
		if (Zend_Registry::isRegistered('Zend_Log'))
		{

			// Ignore Engine_Exception since it has built-in logging
			if (!($exception instanceof Engine_Exception))
			{
				$log = Zend_Registry::get('Zend_Log');
				$output = 'Error Code: ' . self::getErrorCode(true) . PHP_EOL . $exception -> __toString();

				$log -> log($output, Zend_Log::ERR);
			}
		}

		// Uncaught exceptions are always fatal
		// Send nice response for user
		self::_sendFatalResponse();

		return !defined('NUO_ERROR_SILENCE') || NUO_ERROR_SILENCE;
	}

	static public function formatBacktrace($backtrace)
	{
		$output = '';
		$output .= 'Error Code: ' . self::getErrorCode() . PHP_EOL;
		$output .= 'Stack trace:' . PHP_EOL;
		$index = 0;
		foreach ($backtrace as $index => $stack)
		{
			// Process args
			$args = array();
			if (!empty($stack['args']))
			{
				foreach ($stack['args'] as $argIndex => $argValue)
				{
					if (is_object($argValue))
					{
						$args[$argIndex] = get_class($argValue);
					}
					else
					if (is_array($argValue))
					{
						$args[$argIndex] = 'Array';
						//substr(print_r($argValue, true), 0, 32);
					}
					else
					if (is_string($argValue))
					{
						$args[$argIndex] = "'" . substr($argValue, 0, 32) . (strlen($argValue) > 32 ? '...' : '') . "'";
					}
					else
					{
						$args[$argIndex] = print_r($argValue, true);
					}
				}
			}
			// Process message
			$output .= sprintf('#%1$d %2$s(%3$d): %4$s%5$s%6$s(%7$s)', $index, (!empty($stack['file']) ? $stack['file'] : '(unknown file)'), (!empty($stack['line']) ? $stack['line'] : '(unknown line)'), (!empty($stack['class']) ? $stack['class'] : ''), (!empty($stack['type']) ? $stack['type'] : ''), $stack['function'], join(', ', $args));
			$output .= PHP_EOL;
		}

		// Throw main in there for the hell of it
		$output .= sprintf('#%1$d {main}', $index + 1);
		$output = str_replace(ROOT_PATH, '', $output);
		return $output . PHP_EOL;
	}

	static protected function _sendFatalResponse()
	{
		// Clean any previous output from buffer
		while (ob_get_level() > 0)
		{
			ob_end_clean();
		}

		// Make body
		$body = file_get_contents(ROOT_PATH . '/static/html/error.html');

		$body = str_replace('%__ERROR_CODE__%', 'Error code: ' . self::getErrorCode(), $body);

		// Send firephp headers
		if (APPLICATION_ENV == 'development')
		{
			try
			{
				$request = new Zend_Controller_Request_Http();
				$response = new Zend_Controller_Response_Http();

				$headers = Zend_Wildfire_Channel_HttpHeaders::getInstance();
				$headers -> setResponse($response);
				$headers -> setRequest($request);
				$headers -> flush();

				$response -> setBody($body);
				$response -> sendHeaders();
				$response -> sendResponse();

				die(1);
			}
			catch( Exception $e )
			{
				// Will fall through to stuff below
			}
		}

		// Send normal error message
		echo $body;
		die(1);
	}

	/**
	 * Creates a random error code
	 *
	 * @param bool $reset
	 */
	static public function getErrorCode($reset = false)
	{
		if ($reset === true || self::$_errorCode == null)
		{
			$code = md5(uniqid('', true));
			self::$_errorCode = substr($code, 0, 2) . substr($code, 15, 2) . substr($code, 30, 2);
		}
		return self::$_errorCode;
	}

	static public function getTableName($name)
	{
		return self::$_tablePrefix . $name;
	}

	/**
	 * @return string
	 */
	static public function getMode()
	{
		return self::$_sideMode;
	}

	/**
	 * set current running mode
	 * @param string $site_mode
	 * @param void
	 */
	static public function setMode($site_mode)
	{
		self::$_sideMode = $site_mode;
	}

	/**
	 * check is under admin mode
	 * @return TRUE|FALSE
	 */
	static public function isAdmin()
	{
		return self::$_mode == 'admin';
	}

	/**
	 * lower case module name
	 * @param string $module
	 * @return void
	 */
	static public function addModule($moduleName)
	{
		self::$_modules[$moduleName] = $moduleName;
	}

	/**
	 * check if modules is enabled
	 */
	static public function hasModule($moduleName)
	{
		return isset(self::$_modules[$module]);
	}

	/**
	 * @param array $hooks
	 * @return void
	 */
	static public function setHooks($hooks)
	{
		self::$_hooks = $hooks;
	}

	static public function getSettings()
	{
		return new Core_Model_DbTable_Settings;
	}

	/**
	 * get current viewer
	 */
	static public function getViewer()
	{
		if (NULL == self::$_viewer)
		{
			$auth = Zend_Auth::getInstance();
			$id = $auth -> getStorage() -> read();

			$table = Nuo::getTable('user');

			if ($id)
			{
				self::$_viewer = $table -> find((int)$id) -> current();
			}

			if (!is_object(self::$_viewer))
			{
				self::$_viewer = $table -> fetchNew();
			}
		}

		return self::$_viewer;
	}

	/**
	 * remember apis for later usaged.
	 */
	protected static $_coreApis = array();

	/**
	 * get service
	 * @param string $api
	 * @param string $module
	 * @return Object
	 */
	static public function getApi($api, $module = 'Core')
	{
		$class = "{$module}_Api_{$api}";
		return isset(self::$_coreApis[$class]) ? self::$_coreApis[$class] : (self::$_coreApis[$class] = new $class);
	}

	/**
	 * list of DbTable
	 * @property array
	 */
	static protected $_dbtables = array();

	/**
	 * map of item type and db-table name
	 * @property array
	 */
	static protected $_objectTypes = array();

	/**
	 * set all items at first
	 * @property array $object_types
	 * @return vodi
	 */
	static public function setObjectTypes($object_types)
	{
		self::$_objectTypes = $object_types;
	}

	/**
	 * get define of all object type
	 * @return array [$type => dbtable class ]
	 */
	static public function getObjectTypes()
	{
		return self::$_objectTypes;
	}

	/**
	 * @param string $type
	 * @param string $dbtable
	 * @return void
	 */
	static public function setObjectType($object_type, $dbtable)
	{
		self::$_objectTypes[$object_type] = $dbtable;
	}

	/**
	 * @get dbtable by item
	 */
	static public function getTable($type, $module = null)
	{
		$class = NULL == $module ? self::$_objectTypes[$type] : "{$module}_Model_DbTable_{$type}";
		return isset(self::$_dbtables[$class]) ? self::$_dbtables[$class] : (self::$_dbtables[$class] = new $class);
	}

	/**
	 * get item type
	 * if type = null, it get by global id, etc: user:1 => user that have id = 1
	 * @property string $id
	 * @property int $type
	 * @return Nuo_Db_Table_Row|NULL
	 */
	static public function getObject($id, $type = null, $module = null)
	{
		// if type is null, get by global object id, it's normaly in some case.
		if ($type == null)
		{
			list($type, $id) = explode(':', $id);
		}
		return self::getTable($type, $module) -> find($id) -> current();
	}

	/**
	 * page name format must be {$module}_{$controller}_{$action}
	 * @param string $pageName etc: core_index_index,
	 * @return void
	 */
	static public function setPageName($pageName)
	{
		self::$_pageName = $pageName;
	}

	/**
	 * get page name of current process
	 * page name format {$module}_{$controller}_{$action}
	 * @return string
	 */
	static public function getPageName()
	{
		if (self::$_pageName == null)
		{
			throw new Nuo_Exception("Page name has not set at this time");
		}
		return self::$_pageName;
	}

	/**
	 * get mysql date time format
	 * @return datetime
	 */
	static public function getDatetime()
	{
		return date('Y-m-d H:i:s');
	}

	/**
	 * get mysql date time format
	 * @return date
	 */
	static public function getDate()
	{
		return date('Y-m-d H:i:s');
	}

}
